﻿using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Controls;
using WpfGenetic;
using WpfGenetic.Fitness;
using WpfGenetic.Tests;

namespace WpfGenetic.Decoders
{
    public static class Vigenere
    {
        private static readonly double RusIndexOfMatches = 0.0553;     // Индекс совпадений русского языка

        /// <summary>
        /// Дешифрует текст, зашифрованный шифром Виженера, при помощи генетического алгоритма
        /// </summary>
        /// <param name="cryptoText"></param>
        /// <param name="config"></param>
        /// <param name="text"></param>
        /// <param name="token"></param>
        /// <param name="lockObj"></param>
        /// <param name="logDelegate"></param> Отдаёт ему лог
        /// <param name="resultDelegate"></param> Отдаёт ему результат дешифровки
        /// <param name="topFitnessDelegate"></param> Отдаёт ему данные лучшей особи
        /// <exception cref="ArgumentException"></exception>
        public static void Start(string cryptoText, GeneticConfiguration config, out string text, 
                                object lockObj, CancellationToken token, Action<string>? logDelegate = null, 
                                Action<string>? resultDelegate = null, Action<double>? topFitnessDelegate = null,
                                Func<string>? getTimeNow = null)
        {
            text = "";

            if (cryptoText is null || cryptoText.Length == 0) { throw new ArgumentException("Зашифрованный текст пустой"); }
            if (config is null) { throw new ArgumentException("Конфигурация отсутствует"); }

            Random rand = new(DateTime.UtcNow.Millisecond);

            List<VigenereIndividual> individuals;
            if (config.USING_ALPHABET_FOR_GENERATION)
            {
                individuals = PopulationCreator(config.AlphabetSpecial, config.POPULATION_SIZE, config.MIN_START_KEY_LENGTH, config.MAX_START_KEY_LENGTH, config.RANK_KEY_LENGTH);
            }
            else
            {
                individuals = PopulationCreator(config.Alphabet, config.POPULATION_SIZE, config.MIN_START_KEY_LENGTH, config.MAX_START_KEY_LENGTH, config.RANK_KEY_LENGTH);
            }

            List<double> FitnessValues = [];         // Список приспособленностей особей
            List<double> maxFitnessValues = [];      // Максимальная приспособленность в текущей популяции
            List<double> meanFitnessValues = [];     // Средняя приспособленность в текущей популяции

            // Считаем фитнесс-функцию
            CalculateFitness(individuals, cryptoText, config.Alphabet, bigramsType : config.BIGRAMS_TYPE, trigramsType : config.TRIGRAMS_TYPE, indexOfMatchesFlag : config.INDEX_OF_MATCHES_FLAG);

            foreach (VigenereIndividual individual in individuals)
            {
                FitnessValues.Add(individual.Fitness);
            }
            maxFitnessValues.Add(FitnessValues.Max());
            meanFitnessValues.Add(FitnessValues.Sum() / individuals.Count);

            int generationCounter = 0;  // Счётчик поколений

            #region For tests
            // Строка с результатами тестирования
            string tests = "";
            // Процент совпадения с исходным текстом
            double percentage = 0;
            #endregion

            while (generationCounter < config.MAX_GENERATIONS &&
                   !token.IsCancellationRequested &&
                   percentage < 0.99)
            {
                //Блокирующий объект, необходим для приостановки работы алгоритма
                lock(lockObj)
                {
                    generationCounter++;

                    // Турнирный отбор
                    TournamentSelection(individuals, out List<VigenereIndividual> selected, config.SIZE_TOURNAMENT);

                    // Скрещиваем особей
                    int crossoverCount = 0;
                    int selectedSize = selected.Count;
                    int cnt = 0; // Попытки скрещивания
                    while (selected.Count < config.POPULATION_SIZE && cnt < config.POPULATION_SIZE)
                    {
                        int i = rand.Next(selectedSize);
                        int j = rand.Next(selectedSize);
                        // Двое родителей скрещиваются и получается потомок
                        if (i != j && rand.Next(1, 101) <= config.P_CROSSOVER)
                        {
                            cnt++;

                            CrossingOverOnePoint(selected[i], selected[j],
                                pKey: config.P_KEY_CROSSOVER, out VigenereIndividual child);

                            // Проверяем, что такой особи ещё нет в популяции
                            bool flag = true;
                            int count = 0;
                            for (int k = 0; k < selected.Count; k++)
                            {
                                if (child.Key == selected[k].Key)
                                {
                                    count++;
                                    if (count >= config.MAX_REPETITION_OF_INDIVIDUALS)
                                    {
                                        flag = false;
                                        break;
                                    }
                                }
                            }
                            if (flag)
                            {
                                child.Generation = generationCounter;
                                selected.Add(child);
                                crossoverCount++;
                            }
                        }
                    }

                    // Мутируем особей
                    int mutationCount = 0;
                    for (int i = 0; i < selected.Count; i++)
                    {
                        // Лучшие отобранные особи не мутируют
                        if (rand.Next(1, 101) <= config.P_MUTATION && selected[i].Status != IndividualStatus.SelectedTop)
                        {
                            mutationCount++;
                            if (config.USING_ALPHABET_FOR_GENERATION)
                            {
                                Mutation(selected[i], config.AlphabetSpecial, config.P_KEY_MUTATION, config.P_LENGTH_MUTATION, config.P_RAISE_MUTATION, selected[i].Key.Length == 1 ? 1 : selected[i].Key.Length / 2);
                            }
                            else
                            {
                                Mutation(selected[i], config.Alphabet, config.P_KEY_MUTATION, config.P_LENGTH_MUTATION, config.P_RAISE_MUTATION, selected[i].Key.Length == 1 ? 1 : selected[i].Key.Length / 2);
                            }

                            selected[i].Status = IndividualStatus.Mutation;
                        }
                    }

                    // Обновляем список популяции
                    individuals = selected;

                    // Считаем фитнесс-функцию
                    CalculateFitness(individuals, cryptoText, config.Alphabet, bigramsType: config.BIGRAMS_TYPE, trigramsType: config.TRIGRAMS_TYPE, indexOfMatchesFlag: config.INDEX_OF_MATCHES_FLAG);

                    FitnessValues = [];
                    foreach (Individual individual in individuals)
                    {
                        FitnessValues.Add(individual.Fitness);
                    }
                    maxFitnessValues.Add(FitnessValues.Max());
                    meanFitnessValues.Add(FitnessValues.Sum() / individuals.Count);

                    // Выделяем лучших особей
                    if (config.COUNT_OF_MAX_SAVE > 0)
                    {
                        individuals = [.. individuals.OrderByDescending((p) => p.Fitness)];

                        // Вывод суммы биграмм и триграмм лучшей особи на график
                        if (topFitnessDelegate is not null)
                        {
                            topFitnessDelegate(individuals[0].FrequencyOfBigrams + individuals[0].FrequencyOfTrigrams + individuals[0].IndexOfMatches);
                        }

                        for (int i = 0; i < config.COUNT_OF_MAX_SAVE && i < individuals.Count; i++)
                        {
                            individuals[i].Status = IndividualStatus.Top;
                        }
                    }

                    // Выводим информацию в лог
                    if (generationCounter % 100 == 0 && logDelegate is not null)
                    {
                        // Выводим данные в лог
                        string log = $"Поколение {generationCounter}, Максимальная приспособленность {maxFitnessValues[^1]}, Средняя приспособленность {meanFitnessValues[^1]}, " +
                            $"Количество скрещиваний {crossoverCount}, Количество мутаций {mutationCount}\n" +
                            GetLogText(individuals.OrderByDescending(i => i.Fitness).Take(config.CONSOLE_WRITTEN_COUNT).ToList(), cryptoText, config.Alphabet) + "\n\n";

                        logDelegate(log);
                    }

                    #region For testig
                    // Сохраняем значения совпадения текстов для тестирования
                    if (generationCounter % 10 == 0)
                    {
                        var individual = individuals.OrderByDescending(i => i.Fitness).FirstOrDefault();

                        if ( individual is not null)
                        {
                            DecryptVigenere(cryptoText, individual.Key, config.Alphabet, out string decryptText);

                            percentage = Testing.PercentageTextMatching(Testing.Original1000, decryptText);
                        }
                        

                        tests += "\n" + generationCounter.ToString() + "\t" + 
                                 percentage.ToString() + "\t" +
                                 //Testing.GetTests(individuals.OrderByDescending(i => i.Fitness).Take(1).ToList(), cryptoText, config.Alphabet) +
                                 getTimeNow?.Invoke();
                    }
                    #endregion
                }
            }

            // Отправляем результаты лучшей особи
            if (resultDelegate is not null)
            {
                resultDelegate(GetLogText(individuals.OrderByDescending(i => i.Fitness).Take(1).ToList(), cryptoText, config.Alphabet) + "\n" +
                                tests);
            }

            // В случае отмены задачи выбрасываем исключение
            if(token.IsCancellationRequested)
            {
                token.ThrowIfCancellationRequested();
            }
        }

        /// <summary>
        /// Создаёт список индивидов с ключом из символов, входящих в алфавит
        /// maxRank - максимальное количество разрядов в числе для длины ключа
        /// </summary>
        /// <param name="alphabet"></param>
        /// <param name="count"></param>
        /// <param name="minLength"></param>
        /// <param name="maxLength"></param>
        /// <param name="maxRank"></param>
        /// <returns>List<VigenereIndividual></returns>
        /// <exception cref="ArgumentException"></exception>
        private static List<VigenereIndividual> PopulationCreator(string? alphabet, int count = 0, int minLength = 1, int maxLength = 1, int maxRank = 1)
        {
            if (alphabet is null || alphabet.Length == 0) { throw new ArgumentException("Алфавит не может быть пустым"); }
            if (maxLength < minLength) { throw new ArgumentException("Максимальная длина ключа не может быть меньше минимальной длины ключа"); }

            List<VigenereIndividual> population = [];

            Random rand = new(DateTime.UtcNow.Millisecond);
            int randomLength, randomChar;
            StringBuilder key;

            for (int i = 0; i < count; i++)
            {
                // Выбрать случайную длину ключа
                randomLength = rand.Next(minLength, maxLength + 1);
                key = new StringBuilder(randomLength);

                // Собираем ключ из случайных символов алфавита
                for (int j = 0; j < randomLength; j++)
                {
                    randomChar = rand.Next(0, alphabet.Length);
                    key.Append(alphabet[randomChar]);
                }

                population.Add(new VigenereIndividual()
                {
                    Key = key.ToString(),
                    Length = new Binary(randomLength, maxRank),
                    Status = IndividualStatus.New,
                });
            }
            return population;
        }

        /// <summary>
        /// Вычисляет фитнесс функцию по формуле ?????????
        /// и заполняет соответствующие поля в индивидах
        /// Предварительно текст дешифруется ключом индивида
        /// </summary>
        /// <param name="population"></param>
        /// <param name="cryptoText"></param>
        /// <param name="alphabet"></param>
        /// <param name="bigramsType"></param>
        /// <param name="trigramsType"></param>
        /// <param name="indexOfMatchesFlag"></param>
        /// <exception cref="ArgumentException"></exception>
        private static void CalculateFitness(List<VigenereIndividual>? population, string? cryptoText, string? alphabet, int? bigramsType = null, int? trigramsType = null, bool indexOfMatchesFlag = false)
        {
            if (population is null || population.Count == 0) throw new ArgumentException("Популяция не может быть пустой");
            if (cryptoText is null || cryptoText.Length == 0) throw new ArgumentException("Текст не может быть пустым");
            if (alphabet is null || alphabet.Length == 0) throw new ArgumentException("Алфавит не может быть пустым");

            double maxBigrams = 1;
            double maxTrigrams = 1;
            foreach (VigenereIndividual individual in population)
            {
                if (individual.Status != IndividualStatus.SelectedTop &&
                    individual.Status != IndividualStatus.Selected)
                {
                    DecryptVigenere(cryptoText, individual.Key, alphabet, out string text);

                    if (bigramsType is not null)
                    {
                        FrequencyOfBigrams.FindFrequencyOfBigrams(text, out double frequencyOfBigrams, (int)bigramsType);
                        individual.FrequencyOfBigrams = frequencyOfBigrams;

                        if (maxBigrams < frequencyOfBigrams) maxBigrams = frequencyOfBigrams;
                    }
                    else { maxBigrams = 1; }

                    if (trigramsType is not null)
                    {
                        FrequencyOfTrigrams.FindFrequencyOfTrigrams(text, out double frequencyOfTrigrams, (int)trigramsType);
                        individual.FrequencyOfTrigrams = frequencyOfTrigrams;

                        if (maxTrigrams < frequencyOfTrigrams) maxTrigrams = frequencyOfTrigrams;
                    }
                    else { maxTrigrams = 1; }

                    if (indexOfMatchesFlag)
                    {
                        IndexOfMatches.FindFrequency(text, alphabet, out Dictionary<char, int> frequency, out int realLength);
                        IndexOfMatches.FindIndex(frequency, realLength, out double index);
                        individual.IndexOfMatches = index;
                    }
                } 
            }

            foreach (VigenereIndividual individual in population)
            {
                if (indexOfMatchesFlag)
                {
                    individual.Fitness = Math.Round(1 - Math.Abs(individual.IndexOfMatches - RusIndexOfMatches) +
                                                (individual.FrequencyOfBigrams / maxBigrams) +
                                                (individual.FrequencyOfTrigrams / maxTrigrams), 5, MidpointRounding.AwayFromZero);
                }
                else
                {
                    individual.Fitness = (individual.FrequencyOfBigrams / maxBigrams) +
                                     (individual.FrequencyOfTrigrams / maxTrigrams);
                }
            }
        }

        /// <summary>
        /// Дешифрует строку шифром Виженера, используя ключ и алфавит
        /// </summary>
        /// <param name="cipher"></param>
        /// <param name="key"></param>
        /// <param name="alphabet"></param>
        /// <param name="text"></param>
        /// <exception cref="ArgumentNullException"></exception>
        public static void DecryptVigenere(string? cipher, string? key, string? alphabet, out string text)
        {
            text = "";
            if (cipher is null || key is null || alphabet is null)
            {
                throw new ArgumentException("Null reference");
            }

            if (key.Length == 0 || alphabet.Length == 0)
            {
                text = cipher;
                return;
            }

            //Содержит номера букв ключа относительно алфавита, считается, что буквы в алфавите не повторяются
            List<int> codes = [];
            int index;
            foreach (char i in key)
            {
                index = alphabet.IndexOf(i);

                if (index == -1) throw new ArgumentException("Symbol in key non contains in text");

                codes.Add(index);
            }

            //Формируем расшифрованный текст
            var builder = new StringBuilder(cipher.Length);

            int indexKey = 0; // Номер следующей буквы ключа
            int lengthKey = codes.Count; // Количество символов в ключе
            int lengthAlphabet = alphabet.Length; // Количество символов в алфавите

            int buf;
            char ch;
            foreach (char i in cipher)
            {
                index = alphabet.IndexOf(i); // Номер символа в алфавите

                if (index > -1)
                {
                    buf = index - codes[indexKey];

                    if (buf < 0)
                    {
                        buf = lengthAlphabet - (buf % lengthAlphabet) * -1;
                    }
                    ch = alphabet[buf];

                    indexKey = (indexKey + 1) % lengthKey;

                    builder.Append(ch);
                }
                else
                {
                    builder.Append(i);
                }
            }

            text = builder.ToString();

        }

        /// <summary>
        /// Производит турнирный отбор особей по значению фитнесс функции
        /// Численность турнира можно задать (сколько особей будет соревноваться)
        /// </summary>
        /// <param name="population"></param>
        /// <param name="selected"></param>
        /// <param name="size"></param>
        /// <exception cref="ArgumentException"></exception>
        private static void TournamentSelection(List<VigenereIndividual>? population, out List<VigenereIndividual> selected, int size = 2)
        {
            selected = [];

            if (population is null || population.Count == 0) throw new ArgumentException("Популяция не может быть пустой");
            if (size < 2) throw new ArgumentException("Численность турнира не может быть меньше двух");

            // Индексы всей популяции
            List<int> index = [];
            for (int i = 0; i < population.Count; i++)
            {
                if (population[i].Status != IndividualStatus.Top)
                {
                    index.Add(i);
                }
                else
                {
                    population[i].Status = IndividualStatus.SelectedTop;
                    selected.Add(population[i]);
                }

            }

            // Индексы индивидов, участвующих в турнире 
            List<int> sizeIndex;
            // Максимальное значение приспособленности в каждом раунде турнира
            double maxFitness;
            Random rand = new();
            VigenereIndividual individual = new();

            while (index.Count > 0)
            {
                maxFitness = 0;
                sizeIndex = [];

                // Если особей осталось меньше или равное численности турнира, значит участвуют все оставшиеся
                if (index.Count <= size)
                {
                    for (int i = 0; i < index.Count; i++)
                    {
                        sizeIndex.Add(index[i]);
                    }
                    // Обнуляем индексы
                    index = [];
                }
                else
                {
                    int j;
                    // Выбираем случайных size особей
                    for (int i = 0; i < size; i++)
                    {
                        j = rand.Next(index.Count);
                        sizeIndex.Add(index[j]);
                        index.RemoveAt(j);
                    }
                }

                // Сам турнирный отбор
                for (int i = 0; i < sizeIndex.Count; ++i)
                {
                    if (population[sizeIndex[i]].Fitness >= maxFitness)
                    {
                        maxFitness = population[sizeIndex[i]].Fitness;
                        individual = population[sizeIndex[i]];
                    }
                }

                individual.Status = IndividualStatus.Selected;
                selected.Add(individual);
            }
        }

        /// <summary>
        /// Осуществляет скрещивание двух особей
        /// pKey - Вероятность того, что ребёнок получит букву первого родителя;
        /// </summary>
        /// <param name="individual1"></param>
        /// <param name="individual2"></param>
        /// <param name="pKey"></param>
        /// <param name="child"></param>
        /// <exception cref="ArgumentException"></exception>
        private static void CrossingOverOnePoint(VigenereIndividual individual1, VigenereIndividual individual2, double pKey, out VigenereIndividual child)
        {
            child = new();

            if (pKey < 0) throw new ArgumentException("Вероятность не может быть меньше нуля");

            Random rand = new(DateTime.UtcNow.Millisecond);

            //Перемешиваем длины родителей и получаем новую длину
            Binary childBinLength = Binary.Shuffle(individual1.Length, individual2.Length);

            int childLength = childBinLength.GetInt();

            if (childLength == 0)
            {
                childLength = 1;
                childBinLength[0] = true;
            }

            StringBuilder childKey = new(childLength);

            for (int i = 0; i < childLength; i++)
            {
                // Символ какого родителя берём
                if (rand.Next(1, 101) <= pKey)
                {
                    childKey.Append(individual1.Key[i % individual1.Key.Length]);
                }
                else
                {
                    childKey.Append(individual2.Key[i % individual2.Key.Length]);
                }
            }

            child.Key = childKey.ToString();
            child.Length = childBinLength;
            child.Status = IndividualStatus.New;
        }

        /// <summary>
        /// pKey - Вероятность того, что при мутации измениться символ ключа
        /// pLength - Вероятность того, что при мутации измениться длина ключа
        /// pRaise - Вероятность того, что при изменении длины ключа она увеличится на 1 (дополнительный символ берётся из алфавита и встаёт в конец ключа), иначе у ключа удаляется последний символ (если изначально длина 1 ничего не происходит)
        /// </summary>
        /// <param name="individual"></param>
        /// <param name="alphabet"></param>
        /// <param name="pKey"></param>
        /// <param name="pLength"></param>
        /// <param name="pRaise"></param>
        /// <exception cref="ArgumentException"></exception>
        private static void Mutation(VigenereIndividual individual, string? alphabet, double pKey, double pLength, double pRaise, int countKey = 1)
        {
            if (alphabet is null || alphabet.Length == 0) throw new ArgumentException("Алфавит не может быть пустым");
            if (countKey < 0) throw new ArgumentException("Количество изменяемых символов ключа не может быть меньше 0");

            Random rand = new();

            // Меняем символ ключа
            for (int i = 0; i < countKey; i++)
            {
                if (rand.Next(0, 101) <= pKey)
                {
                    char randomChar = alphabet[rand.Next(0, alphabet.Length)];

                    StringBuilder newKey = new(individual.Key);

                    newKey[rand.Next(0, newKey.Length)] = randomChar;

                    individual.Key = newKey.ToString();
                }
            }

            // Меняем длину ключа
            if (rand.Next(1, 101) <= pLength)
            {
                // Инвертируем 1 бит длины ключа
                // Номер инвертируемого бита
                int randomCount = rand.Next(individual.Length.Count);

                individual.Length[randomCount] = !individual.Length[randomCount];

                int mutationLength = individual.Length.GetInt();

                if (mutationLength == 0)
                {
                    mutationLength = 1;
                    individual.Length[0] = true;
                }

                if (mutationLength < individual.Key.Length)
                {
                    StringBuilder newKey = new(individual.Key);

                    // Удаляем последние символы
                    for (int i = mutationLength; i < individual.Key.Length; i++)
                    {
                        newKey.Length--; // Удаляем последний символ
                    }

                    individual.Key = newKey.ToString();
                }
                else
                {
                    StringBuilder newKey = new(individual.Key);
                    char randomChar;

                    // Добавляем случайные символы
                    for (int i = individual.Key.Length; i < mutationLength; i++)
                    {
                        randomChar = alphabet[rand.Next(0, alphabet.Length)];
                        newKey.Append(randomChar);
                    }

                    individual.Key = newKey.ToString();
                }

                /*
                // Повышаем
                if (rand.Next(1, 101) <= pRaise)
                {
                    char randomChar = alphabet[rand.Next(0, alphabet.Length)];

                    StringBuilder newKey = new(individual.Key);

                    newKey.Append(randomChar);

                    individual.Key = newKey.ToString();

                    individual.Length.Inc();
                }
                // Или понижаем
                else if (individual.Key.Length > 1)
                {
                    StringBuilder newKey = new(individual.Key);

                    newKey.Length--; // Удаляем последний символ

                    individual.Key = newKey.ToString();

                    individual.Length.Dec();
                }
                */
            }
        }

        private static string GetLogText(List<VigenereIndividual> individuals, string cryptoText, string alphabet)
        {
            if (cryptoText is null || cryptoText.Length == 0) throw new ArgumentException("Текст не может быть пустым");
            if (alphabet is null || alphabet.Length == 0) throw new ArgumentException("Алфавит не может быть пустым");

            string log = "";
            int number = 0;

            foreach (var individual in individuals)
            {
                number++;
                DecryptVigenere(cryptoText, individual.Key, alphabet, out string text);
                log +=  $"{number}) Ключ = '{individual.Key}'; Поколение = '{individual.Generation}'; Индекс = '{individual.IndexOfMatches}'" +
                    $"; Биграммы: '{individual.FrequencyOfBigrams}'; Триграммы: '{individual.FrequencyOfTrigrams}'\nФитнесс функция: '{individual.Fitness}';\n Дешифрованная строка = '{text}'\n\n";
            }

            return log;
        }
    }
}
